// Kokomi.js - Three.js 组件框架
import * as kokomi from "kokomi.js";
// Three.js - 3D 渲染引擎
import * as THREE from "three";

// 网格反射材质配置接口
export interface MeshReflectorMaterialConfig {
  resolution: number; // 反射纹理分辨率
  ignoreObjects: THREE.Object3D[]; // 忽略的对象（不参与反射）
}

/**
 * 网格反射材质
 * 用于实现地板的实时反射效果
 * 原理：创建一个虚拟相机，从反射平面的对称位置渲染场景
 */
class MeshReflectorMaterial extends kokomi.Component {
  parent: THREE.Mesh; // 父网格（反射平面）
  ignoreObjects: THREE.Object3D[]; // 忽略的对象列表
  _camera: THREE.PerspectiveCamera; // 虚拟反射相机
  reflectPlane: THREE.Plane; // 反射平面
  _reflectMatrix: THREE.Matrix4; // 反射矩阵
  _renderTexture: kokomi.FBO; // 反射渲染纹理
  mipmapper: kokomi.PackedMipMapGenerator; // Mipmap 生成器
  mirrorFBO: THREE.WebGLRenderTarget; // 镜像帧缓冲对象
  mipmapFBO: kokomi.FBO; // Mipmap 帧缓冲对象
  
  constructor(
    base: kokomi.Base,
    parent: THREE.Mesh,
    config: Partial<MeshReflectorMaterialConfig> = {}
  ) {
    super(base);

    // 保存父网格引用
    this.parent = parent;

    // 从配置中获取参数
    let { resolution = 256, ignoreObjects = [] } = config;

    // 保存忽略对象列表
    this.ignoreObjects = ignoreObjects;

    // 创建虚拟反射相机
    this._camera = new THREE.PerspectiveCamera();
    // 创建反射平面（默认在 Y=0 平面）
    this.reflectPlane = new THREE.Plane();
    // 创建反射矩阵
    this._reflectMatrix = new THREE.Matrix4();
    // 创建反射渲染纹理（FBO）
    this._renderTexture = new kokomi.FBO(this.base, {
      width: resolution,
      height: resolution,
      options: {
        type: THREE.UnsignedByteType,
      },
    });

    // 创建 Mipmap 生成器（用于优化性能）
    const mipmapper = new kokomi.PackedMipMapGenerator();
    this.mipmapper = mipmapper;
    // 保存镜像 FBO 引用
    const mirrorFBO = this._renderTexture.rt;
    this.mirrorFBO = mirrorFBO;
    // 创建 Mipmap FBO
    const mipmapFBO = new kokomi.FBO(this.base);
    this.mipmapFBO = mipmapFBO;
  }
  /**
   * 每帧更新
   * 渲染反射纹理并生成 Mipmap
   */
  update(): void {
    // 渲染反射纹理
    this.beforeRender();

    // 生成 Mipmap（用于模糊和性能优化）
    this.mipmapper.update(
      this.mirrorFBO.texture,
      this.mipmapFBO.rt,
      this.base.renderer
    );
  }
  /**
   * 渲染反射纹理
   * 使用虚拟相机从反射平面的对称位置渲染场景
   */
  beforeRender() {
    // ==================== 1. 设置反射平面 ====================
    // 创建平面（法线向上 Y 轴，距离原点 0）
    this.reflectPlane.set(new THREE.Vector3(0, 1, 0), 0);
    // 应用父网格的世界变换
    this.reflectPlane.applyMatrix4(this.parent.matrixWorld);
    
    // ==================== 2. 复制主相机属性 ====================
    // @ts-ignore
    this._camera.copy(this.base.camera);
    
    // ==================== 3. 计算反射相机位置 ====================
    // 获取主相机世界位置
    const r = new THREE.Vector3(0, 0, 1).clone().negate();
    const o = this.base.camera.getWorldPosition(new THREE.Vector3());
    // 反射向量
    r.reflect(this.reflectPlane.normal);
    // 将主相机位置投影到反射平面上
    const p = new THREE.Vector3();
    this.reflectPlane.projectPoint(o, p);
    // 计算反射相机位置（对称位置）
    const y = p.clone();
    y.sub(o), y.add(p), this._camera.position.copy(y);
    
    // ==================== 4. 计算反射相机注视点 ====================
    const d = new THREE.Vector3(0, 0, -1);
    d.applyQuaternion(
      this.base.camera.getWorldQuaternion(new THREE.Quaternion())
    );
    d.add(o);
    const E = new THREE.Vector3();
    this.parent.getWorldPosition(E);
    E.sub(d);
    E.reflect(this.reflectPlane.normal).negate();
    E.add(this.parent.getWorldPosition(new THREE.Vector3()));
    
    // ==================== 5. 设置反射相机方向 ====================
    this._camera.up.set(0, 1, 0);
    this._camera.applyQuaternion(
      this.base.camera.getWorldQuaternion(new THREE.Quaternion())
    );
    // 反射 up 向量
    this._camera.up.reflect(this.reflectPlane.normal);
    this._camera.lookAt(E);
    this._camera.updateMatrixWorld();
    
    // ==================== 6. 计算反射矩阵 ====================
    // 创建纹理坐标变换矩阵（0-1 映射）
    const L = new THREE.Matrix4();
    L.set(0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1);
    L.multiply(this._camera.projectionMatrix);
    L.multiply(this._camera.matrixWorldInverse);
    this._reflectMatrix.copy(L);
    
    // ==================== 7. 应用斜裁剪平面 ====================
    // 将反射平面转换到相机空间
    this.reflectPlane.applyMatrix4(this._camera.matrixWorldInverse);
    const k = new THREE.Vector4(
      this.reflectPlane.normal.x,
      this.reflectPlane.normal.y,
      this.reflectPlane.normal.z,
      this.reflectPlane.constant
    );
    // 修改投影矩阵以裁剪反射平面下方的物体
    const X = this._camera.projectionMatrix;
    const $ = new THREE.Vector4();
    $.x = (Math.sign(k.x) + X.elements[8]) / X.elements[0];
    $.y = (Math.sign(k.y) + X.elements[9]) / X.elements[5];
    $.z = -1;
    $.w = (1 + X.elements[10]) / X.elements[14];
    k.multiplyScalar(2 / k.dot($));
    X.elements[2] = k.x;
    X.elements[6] = k.y;
    X.elements[10] = k.z + 1;
    X.elements[14] = k.w;
    
    // ==================== 8. 渲染反射纹理 ====================
    // 保存当前渲染目标
    const Z = this.base.renderer.getRenderTarget();
    // 切换到反射纹理渲染目标
    this.base.renderer.setRenderTarget(this._renderTexture.rt);
    // 启用深度写入
    this.base.renderer.state.buffers.depth.setMask(true);
    // 清除缓冲区
    this.base.renderer.autoClear === false && this.base.renderer.clear();
    // 隐藏忽略的对象
    this.ignoreObjects.forEach((be) => (be.visible = false));
    // 使用虚拟相机渲染场景
    this.base.renderer.render(this.base.scene, this._camera);
    // 恢复忽略对象的可见性
    this.ignoreObjects.forEach((be) => (be.visible = true));
    // 恢复原始渲染目标
    this.base.renderer.setRenderTarget(Z);
  }
}

export { MeshReflectorMaterial };
